package cosutils

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"path"
	"reflect"
	"strings"
	"time"
	"pd/server/com"
	"pd/server/plugin/logger"

	"github.com/imroc/req"
	"github.com/tealeg/xlsx"
	"github.com/tencentyun/cos-go-sdk-v5"
	"github.com/valyala/bytebufferpool"
)

type XObjectGetOptions struct {
	cos.ObjectGetOptions
	ExpiresTime int64 // 过期时间，毫秒
}

// isCache=true 表示使用缓存桶，缓存桶中的对象可能会被定期清理
// 默认使用资源桶，资源桶的对象将长期有效
func GetClient(isCache ...bool) *cos.Client {
	u, _ := url.Parse("https://src-1304305412.cos.ap-guangzhou.myqcloud.com")
	b := &cos.BaseURL{BucketURL: u}
	if len(isCache) > 0 && isCache[0] {
		u, _ = url.Parse("https://tmp-1304305412.cos.ap-guangzhou.myqcloud.com")
		b = &cos.BaseURL{BucketURL: u}
	}

	c := cos.NewClient(b, &http.Client{
		Transport: &cos.AuthorizationTransport{
			SecretID:  "AKIDZ0DE1f9QTIAyaz93h7UowoQwOfjjdL3Z",
			SecretKey: "LLBzpYDlKfngJjvvupeFeqoY3KL8jNo3",
			//Transport: &debug.DebugRequestTransport{
			//	RequestHeader:  true,
			//	RequestBody:    true,
			//	ResponseHeader: true,
			//	//ResponseBody:   true,
			//},
		},
	})

	return c
}

func GetCosBaseURL(isCache ...bool) string {
	if len(isCache) > 0 && isCache[0] {
		return "http://file2.bidevice.com" // 缓存桶
	}

	return "http://file.bidevice.com" // 资源桶（永久）
}

func ListObject(prefixKey string, opt *cos.BucketGetOptions) (*cos.BucketGetResult, error) {
	c := GetClient()

	if opt == nil {
		opt = &cos.BucketGetOptions{}
	}

	opt.Prefix = prefixKey

	if result, response, e := c.Bucket.Get(context.Background(), opt); e != nil {
		return result, e
	} else {
		if response.StatusCode == 404 {
			logger.Info(404)
		}

		return result, nil
	}
}

func Get(key string, opt *XObjectGetOptions) (*string, error) {
	c := GetClient()

	var opts *cos.ObjectGetOptions
	if opt != nil {
		opts = &opt.ObjectGetOptions
	}

	resp, err := c.Object.Get(context.Background(), key, opts)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	buf := bytebufferpool.Get()
	if _, err := buf.ReadFrom(resp.Body); err != nil {
		return nil, err
	}

	defer bytebufferpool.Put(buf)

	rs := string(buf.Bytes())

	return &rs, nil
}

func GetBody(key string, opt *XObjectGetOptions) (io.ReadCloser, error) {
	c := GetClient()

	var opts *cos.ObjectGetOptions
	if opt != nil {
		opts = &opt.ObjectGetOptions
	}

	resp, err := c.Object.Get(context.Background(), key, opts)
	if err != nil {
		return nil, err
	}

	return resp.Body, nil
}

func GetTmpPath(basePath string) string {
	if basePath == "" {
		basePath = "/tmp"
	}

	filepath := path.Join(basePath, fmt.Sprintf("%s", time.Now().Format("200601020304")))

	return filepath
}

func DownFileToTmp(key string, opt *cos.ObjectGetOptions) (string, *cos.Response, error) {
	var retry = 3

	tmpPath := GetTmpPath("")

	for {
		retry--
		if file, response, err := DownFile(key, tmpPath, opt); err != nil {
			if retry <= 0 {
				return file, response, err
			} else {
				continue
			}
		} else {
			return file, response, err
		}
	}
}

func DownFile(key string, toPath string, opt *cos.ObjectGetOptions) (string, *cos.Response, error) {
	c := GetClient()

	if response, err := c.Object.GetToFile(context.Background(), key, toPath, opt); err != nil {
		return toPath, nil, err
	} else {
		response.Header.Set("___to_path", toPath)
		return toPath, response, err
	}
}

func Head(key string, opt *cos.ObjectHeadOptions) (*cos.Response, error) {
	c := GetClient()

	return c.Object.Head(context.Background(), key, opt)
}

func Exists(key string) (bool, error) {
	if response, e := Head(key, nil); e != nil {
		if response != nil && response.StatusCode == http.StatusNotFound {
			return false, nil
		}

		return false, e
	}

	return true, nil
}

func HeadContentLength(key string) (int64, error) {
	if response, e := Head(key, nil); e != nil {
		if response != nil && response.StatusCode == http.StatusNotFound {
			return 0, nil
		}

		return 0, e
	} else {
		contentLength := response.Header.Get("Content-Length")
		cLength, _ := com.ToInt64(contentLength)
		return cLength, e
	}
}

func HeadEtag(key string) (string, error) {
	if response, e := Head(key, nil); e != nil {
		if response != nil && response.StatusCode == http.StatusNotFound {
			return "", nil
		}

		return "", e
	} else {
		etag := response.Header.Get("Etag")
		if etag != "" {
			etag = strings.Trim(etag, `"`)
		}

		return etag, e
	}
}

func PutFromReader(key string, reader io.Reader, opt *cos.ObjectPutOptions, isCache ...bool) (*cos.Response, error) {
	c := GetClient(isCache...)

	if response, err := c.Object.Put(context.Background(), key, reader, opt); err != nil {
		return nil, err
	} else {
		return response, err
	}
}

func Put(key string, content string, opt *cos.ObjectPutOptions, isCache ...bool) (*cos.Response, error) {
	c := GetClient(isCache...)

	if response, err := c.Object.Put(context.Background(), key, strings.NewReader(content), opt); err != nil {
		return nil, err
	} else {
		return response, err
	}
}

func PutFile(name string, filePath string, opt *cos.ObjectPutOptions, isCache ...bool) (*cos.Response, error) {
	c := GetClient(isCache...)

	if response, err := c.Object.PutFromFile(context.Background(), name, filePath, opt); err != nil {
		return nil, err
	} else {
		return response, err
	}
}

func PutFileUrl(name string, urlPath string, opt *cos.ObjectPutOptions, setClient func(client *http.Client), isCache ...bool) (*cos.Response, error) {
	c := GetClient(isCache...)

	var read io.ReadCloser

	//progress := func(current, total int64) {
	//	fmt.Println(float32(current)/float32(total)*100, "%")
	//}

	if opt == nil {
		opt = &cos.ObjectPutOptions{
			ACLHeaderOptions: &cos.ACLHeaderOptions{
				XCosACL: "public-read",
				//XCosACL: "private",
			},
		}
	}

	//if opt.ObjectPutHeaderOptions == nil {
	//opt.ObjectPutHeaderOptions = &cos.ObjectPutHeaderOptions{
	//	//ContentType: "text/html",
	//}
	//}

	client := &http.Client{}

	if setClient != nil {
		setClient(client)
	}

	//if resp, err := http.Get(urlPath); err != nil {
	//	return nil, err
	//} else {
	//	read = resp.Body
	//	//opt.ObjectPutHeaderOptions.ContentLength = int(resp.ContentLength)
	//	defer read.Close()
	//}

	if request, err := req.Get(urlPath, client); err != nil {
		return nil, err
	} else {
		read = request.Response().Body
		//opt.ObjectPutHeaderOptions.ContentLength = int(request.Response().ContentLength)
		defer read.Close()
	}

	if response, err := c.Object.Put(context.Background(), name, read, opt); err != nil {
		return nil, err
	} else {
		return response, err
	}
}

func UploadFileSpecKey(fileKey string, file io.Reader, contentType string, isCache ...bool) (string, error) {
	client := GetClient(isCache...)
	opt := &cos.ObjectPutOptions{
		ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
			ContentType: contentType,
			//ContentType: "Image/png",
		},
		ACLHeaderOptions: &cos.ACLHeaderOptions{
			XCosACL: "public-read",
		},
	}
	if _, err := client.Object.Put(context.Background(), fileKey, file, opt); err != nil {
		return "", err
	}

	return fmt.Sprintf("%s/%s", GetCosBaseURL(isCache...), fileKey), nil
}

func UploadFile(key string, file io.Reader, contentType string, isCache ...bool) (string, error) {
	fileKey, _ := com.GetTmpFilePath("ddy_" + key)

	client := GetClient(isCache...)
	opt := &cos.ObjectPutOptions{
		ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
			ContentType: contentType,
			//ContentType: "Image/png",
		},
		ACLHeaderOptions: &cos.ACLHeaderOptions{
			XCosACL: "public-read",
		},
	}
	if _, err := client.Object.Put(context.Background(), fileKey, file, opt); err != nil {
		return "", err
	}

	return fmt.Sprintf("%s/%s", GetCosBaseURL(isCache...), fileKey), nil
}

type BeforeExportExcelFn func(sheet *xlsx.Sheet)
type ExportRowHandleFn func(index int, data map[string]interface{})

// 导出excel并上传
func ExportToExcel(itemList interface{}, headerList []string, headerName map[string]string, key string, beforeFn BeforeExportExcelFn, handle ...ExportRowHandleFn) (fileURL string, err error) {
	t1 := reflect.TypeOf(itemList)
	if itemList == nil || t1.Kind() != reflect.Slice {
		err = fmt.Errorf("itemList type(%s) is not slice", t1.Kind().String())
		return
	}

	xlsxFile := xlsx.NewFile()
	sheet, err := xlsxFile.AddSheet("Sheet1")
	if err != nil {
		return
	}

	if beforeFn != nil {
		beforeFn(sheet)
	}

	headerRow := sheet.AddRow()
	for _, field := range headerList {
		headerRow.AddCell().SetString(headerName[field])
	}

	v1 := reflect.ValueOf(itemList)
	var mpValue map[string]interface{}
	for i := 0; i < v1.Len(); i++ {
		v2 := v1.Index(i)
		if v2.Kind() == reflect.Ptr {
			v2 = v2.Elem()
		}
		mpValue, err = com.StructToMapInterface(v2.Interface())
		if err != nil {
			return
		}

		if len(handle) > 0 { // 对数据进行处理
			handle[0](i, mpValue)
		}

		row := sheet.AddRow()
		for _, field := range headerList {
			row.AddCell().SetString(com.ToString(mpValue[field]))
		}
	}

	buffer := &bytes.Buffer{}
	if err = xlsxFile.Write(buffer); err != nil {
		return
	}

	return UploadFile(key, buffer, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", true)
}

// 上传excel文件 后缀为xlsx
func UploadExportXlsx(fileByte io.Reader, key string, isCache ...bool) (string, error) {
	fileKey, _ := com.GetTmpFilePath(key)

	client := GetClient(isCache...)
	opt := &cos.ObjectPutOptions{
		ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
			ContentType: "application/octet-stream",
			XCosMetaXXX: &http.Header{"Content-Disposition": []string{fmt.Sprintf("attachemnt;filename=\"%s\".xlsx", fileKey)}},
		},
		ACLHeaderOptions: &cos.ACLHeaderOptions{
			XCosACL: "public-read",
		},
	}
	if _, err := client.Object.Put(context.Background(), fileKey, fileByte, opt); err != nil {
		return "", err
	}

	return fmt.Sprintf("%s/%s", GetCosBaseURL(isCache...), fileKey), nil
}
